package com.xlabs.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import com.xlabs.client.Elements.TextLink;
import com.xlabs.client.richtext.ImageButton;
import com.xlabs.client.DTO.ReplyDTO;
import com.xlabs.client.DTO.QuestionDTO;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Conversation view class.
 */
public class Conversation extends Composite {
    /**
     * Conversation Tree.
     */
    private ScrollPanel treeScroll = new ScrollPanel();

    /**
     * ConversationPanel.
     */
    private HorizontalPanel conversationPanel = new HorizontalPanel();

    /**
     * Conversation scroll panel.
     */
    private ScrollPanel conversationScroll = new ScrollPanel();

    /**
     * Corresponding question encoded key.
     */
    private QuestionDTO questionDTO;
    
    /**
     * Maps Reply Data transfer objects to corresponding tree item.
     */
    private Map<ReplyDTO, TreeItem> replyDTOTreeItemMap = new HashMap<ReplyDTO, TreeItem>();

    /**
     * Initializes Conversation.
     */
    public Conversation()
    {
        initWidget(this.conversationPanel);
        
        final int height = 715;
        
        setSize("750", String.valueOf(height));
        conversationPanel.add(this.conversationScroll);
        conversationPanel.add(this.treeScroll);
        conversationScroll.setSize("500", String.valueOf(height));
        treeScroll.setSize("250", String.valueOf(height));
        treeScroll.addStyleName("ScrollPanel");
        conversationScroll.addStyleName("ScrollPanel");
    }

    /**
     * Gets {@link #questionDTO}
     * @return {@link #questionDTO}
     */
    public QuestionDTO getQuestionDTO() {
        return questionDTO;
    }
    
    /**
     * Shows conversation for the given parent.
     *
     * @param questionDTO parent question's dto.
     */
    public void showConversation(QuestionDTO questionDTO) {
        this.questionDTO = questionDTO;

        CommentingServiceAsync commentingService = (CommentingServiceAsync) GWT.create(CommentingService.class);
        commentingService.getConversation(questionDTO.getEncodedKey(), new ConversationAsyncCalBack(this));
    }


    /**
     * Gets conversation scroll.
     * @return conversation scroll.
     */
    public ScrollPanel getConversationScroll() {
        return conversationScroll;
    }

    /**
     * Gets conversation grid.
     * @return conversation grid.
     */
    public Grid getConversationGrid() {
        return (Grid) conversationScroll.getWidget();
    }

    /**
     * Sets conversationGrid.
     *
     * @param conversationGrid Conversation grid.
     */
    public void setConversationGrid(Grid conversationGrid) {
        conversationGrid.getColumnFormatter().setWidth(0, "350");
        
        this.conversationScroll.clear();
        this.conversationScroll.add(conversationGrid);

        this.refreshConversationPanel();
    }

    /**
     * Refreshes conversation panel.
     */
    private void refreshConversationPanel() {
        this.conversationPanel.clear();
        this.conversationPanel.add(this.conversationScroll);
        this.conversationPanel.add(this.treeScroll);
    }

    /**
     * Sets conversation tree.
     * @param conversationTree tree of conversation.
     */
    public void setConversationTree(Tree conversationTree) {
        this.treeScroll.clear();

        this.treeScroll.add(conversationTree);

        this.refreshConversationPanel();
    }

	/**
	 * Sets {@link #replyDTOTreeItemMap}
	 * @param replyDTOTreeItemMap mapping from reply DTO to tree item.
	 */
    public void setReplyDTOTreeItemMap(Map<ReplyDTO, TreeItem> replyDTOTreeItemMap) {
		this.replyDTOTreeItemMap = replyDTOTreeItemMap;
	}

	/**
	 * Gets {@link #replyDTOTreeItemMap}
	 * @return mapping from reply DTO to tree item.
	 */
    public Map<ReplyDTO, TreeItem> getReplyDTOTreeItemMap() {
		return replyDTOTreeItemMap;
	}
}

/**
 * ConversationAsyncCalBack class.
 */
class ConversationAsyncCalBack implements AsyncCallback {
    /**
     * Corresponding conversation object.
     */
    private Conversation conversation;

    public ConversationAsyncCalBack(Conversation conversation) {
        this.conversation = conversation;
    }

    /**
     * On failure.
     *
     * @param throwable exception thrown by server.
     */
    public void onFailure(Throwable throwable) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    /**
     * onSuccess.
     *
     * @param o Object returned from server.
     */
    @SuppressWarnings("unchecked")
	public void onSuccess(Object o) {
        List<ReplyDTO> replies = (List<ReplyDTO>) o;

        Tree newTree = this.createConversationTree(replies);
        Grid grid = this.createConversationGrid(replies);
        
        this.conversation.setConversationGrid(grid);
        this.conversation.setConversationTree(newTree);
    }

    /**
     * Creates conversation grid and tree.
     * @param replies replies received.
     * @return grid created from the given replies.
     */
    private Grid createConversationGrid(List<ReplyDTO> replies) 
    {
        Grid grid = new Grid(replies.size(), 3);
        int index = 0;
        
        for (ReplyDTO reply : replies) {
        	
        	if (reply.getParent() == null)
        	{
	            index = buildGrid(reply, grid, index, 0);
        	}
        }
        
        return grid;
    }

    /**
     * Appends the given reply to the given grid at the given index.
     * @param reply Input reply.
     * @param grid Grid to add to.
     * @param index Where to add the reply in the grid.
     * @param level The level of reply in hierarchy.
     * @return The index of last row inserted.
     */
    private int buildGrid(ReplyDTO reply, Grid grid, int index, int level) 
    {
    	TreeItem replyNode = this.conversation.getReplyDTOTreeItemMap().get(reply);
    	replyNode.setUserObject(new Integer(index));
    	
        createGridRow(reply, grid, index, level);
        
        index ++;
        
        for (ReplyDTO innerReply : reply.getReplies())
        {
        	index = buildGrid(innerReply, grid, index, level + 1);
        }
        
        return index;
	}

	/**
	 * Creates row in the grid based on given reply at given index.
	 * @param reply Input reply.
	 * @param grid Grid to add the row to.
	 * @param index Index of row.
	 * @param level The level of this reply in hierarchy.
	 */
	private void createGridRow(ReplyDTO reply, Grid grid, Integer index, int level) 
	{
		String indentation = "";
		for (int i = 0; i < level;i ++)
		{
			indentation = indentation + "...";
		}
		
		grid.setWidget(index, 0, new Label(indentation + reply.getReplyText()));

        Label userLabel = new Label(reply.getCreatedBy());
        grid.setWidget(index, 1, userLabel);

        ImageButton replyButton = new ImageButton("Email-Reply-icon.png", "");
        replyButton.addClickListener(new InnerReplyClickListener(index, grid, reply.getEncodedKey()));
        grid.setWidget(index, 2, replyButton);

        grid.getCellFormatter().addStyleName(index, 0, "GridCell");
        grid.getCellFormatter().addStyleName(index, 1, "GridCell");
        grid.getCellFormatter().addStyleName(index, 2, "GridCell");
	}

	/**
     * Initializes the conversation tree.
     * @param replies list of replies received.
     * @return Initializes tree.
     */
    private Tree createConversationTree(List<ReplyDTO> replies) {
    	
        Tree newTree = new Tree();
        QuestionDTO questionDTO = this.conversation.getQuestionDTO();
        this.conversation.getReplyDTOTreeItemMap().clear();

        DateTimeFormat sdf = DateTimeFormat.getFormat("MMM dd");
        String rootCaption = questionDTO.getCreatedBy() + " " + sdf.format(questionDTO.getCreatedOn());

        TreeItem rootNode = new TreeItem(rootCaption);
        newTree.addItem(rootNode);
        
        for (ReplyDTO reply : replies) {
        	// add direct replies of the question.
            if (reply.getParent() == null) {
                TreeItem childNodeOfRoot = new TreeItem(getTreeItemCaption(reply));
                rootNode.addItem(childNodeOfRoot);
                this.conversation.getReplyDTOTreeItemMap().put(reply, childNodeOfRoot);
                buildSubtree(childNodeOfRoot, reply);
            }
        }

        newTree.addTreeListener(new ConversationTreeListener());

        return newTree;
    }

    /**
     * Builds sub tree recursively.
     * @param rootNode root node of this subtree.
     * @param reply The reply object that corresponds to the root node of this subtree.
     */
    private void buildSubtree(
    		TreeItem rootNode, 
			ReplyDTO reply) 
    {
    	
    	for (ReplyDTO childReplyDTO : reply.getReplies())
    	{
    		TreeItem childReplyNode = new TreeItem(getTreeItemCaption(childReplyDTO));
    		rootNode.addItem(childReplyNode);
    		this.conversation.getReplyDTOTreeItemMap().put(childReplyDTO, childReplyNode);
    		buildSubtree(childReplyNode, childReplyDTO);
    	}
	}

	/**
     * Gets the node string for a given comment.
     * @param reply input comment.
     * @return node string.
     */
    private String getTreeItemCaption(ReplyDTO reply) 
    {
        DateTimeFormat sdf = DateTimeFormat.getFormat("MMM dd");
        return reply.getCreatedBy() + " " + sdf.format(reply.getCreatedOn());
    }

    /**
     * ReplyListener class.
     */
    private class InnerReplyClickListener implements ClickListener {

        /**
         * index of reply.
         */
        private int index;

        /**
         * Comments grid.
         */
        private Grid commentsGrid;

        /**
         * Question key.
         */
        private String parentReplyKey;

        /**
         * Initializes an instance of ReplyListener.
         *
         * @param index        index of row clicked.
         * @param commentsGrid The comments grid.
         * @param parentReplyKey  key of the question.
         */
        public InnerReplyClickListener(int index, Grid commentsGrid, String parentReplyKey) {
            this.index = index;
            this.commentsGrid = commentsGrid;
            this.parentReplyKey = parentReplyKey;
        }

        /**
         * Onclick.
         *
         * @param widget widget.
         */
        public void onClick(Widget widget) {
            Label commentLAbel = (Label) this.commentsGrid.getWidget(index, 0);
            VerticalPanel replyBox = new VerticalPanel();
            replyBox.add(commentLAbel);

            final TextArea replyArea = new TextArea();
            replyArea.setSize("300px", "100px");
            replyBox.add(replyArea);

            Button submitButton = new Button("Submit", new ClickListener() {
                public void onClick(Widget widget) {
                    CommentingServiceAsync myService = (CommentingServiceAsync) GWT.create(CommentingService.class);
                    myService.posInnertReply(
                            replyArea.getText(),
                            InnerReplyClickListener.this.parentReplyKey,
                            ConversationAsyncCalBack.this.conversation.getQuestionDTO().getEncodedKey(),
                            new AsyncCallback() {

                        public void onFailure(Throwable throwable) {
                            replyArea.setText("Failed to post Reply : " + replyArea.getText() + " Because of " + throwable);
                        }

                        public void onSuccess(Object o) {
                            replyArea.setText("Posted Reply : " + replyArea.getText());
                            replyArea.setReadOnly(true);
                        }
                    });
                }
            });

            replyBox.add(submitButton);

            this.commentsGrid.setWidget(index, 0, replyBox);
        }
    }


    /**
     * The ConversationTreeListener class.
     */
    private class ConversationTreeListener implements TreeListener {
        /**
         * OnTreeItemSelected.
         * @param treeItem tree item selected.
         */
        public void onTreeItemSelected(TreeItem treeItem) {
            if (treeItem.getUserObject() != null)
            {
                int index = (Integer) treeItem.getUserObject();

                Conversation conversation = ConversationAsyncCalBack.this.conversation;
                conversation.getConversationScroll().ensureVisible(conversation.getConversationGrid().getWidget(index, 0));
            }
        }

        /**
         * onTreeItemStateChanged method.
         * @param treeItem tree item whose state has changed.
         */
        public void onTreeItemStateChanged(TreeItem treeItem) {
            //To change body of implemented methods use File | Settings | File Templates.
        }
    }
}

